/**
 * @file ProductCommandImpl.java
 * Projet eCOM 2010 (Miage Descartes)
 * @author Doido Sébastien
 * @version 2
 * @brief Shell : Gestion des fonctionnalités relatives aux produits
 */
package ecom.shell;

import ecom.beans.EcomAdminRemote;
import ecom.beans.EcomCustomerRemote;
import ecom.beans.ProductBean;
import ecom.beans.UnknownIdentifier;
import ecom.beans.WarehouseLineBean;
import ecom.shell.OutputCommandImpl.Outputs;
import ecom.shell.ShellConstantes.RESULT_ACTION;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.StringTokenizer;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

public class ProductCommandImpl implements ShellCommand {

    private ShellContext _context = null;

    public ProductCommandImpl(ShellContext context) {
        _context = context;
    }

    /**
     * @brief Renvoie le nom de la commande
     * @return le nom de la commande
     */
    public String getName() {
        return "product";
    }

    /**
     * @brief Renvoie la manière d'utiliser la commande
     * @return la manière d'utiliser la commande
     */
    public String getUsage() {
        return "product";
    }

    /**
     * @brief Renvoie la description de la commande
     * @return la description de la commande
     */
    public String getShortDescription() {
        String description = "Product Management\n";
        if (true == _context.getVar("adminMode")) {
            description += "\t  : -add [name] [desc] [category ID]..........................................create a new product\n";
            description += "\t  : -remove [product ID]......................................................delete a product\n";
            description += "\t  : -addStock .p [product ID] .s [store ID] .q [quantity] .price [price]......add a new warehouseline\n";
        }
        description += "\t  : -id [product ID]..........................................................display specified product details\n";
        description += "\t  : -categ [categ ID].........................................................products of a specified category\n";
        description += "\t  : -store [store ID].........................................................products of a specified store\n";
        description += "\t  : -name [product name]......................................................products fitting the specified name\n";
        description += "\t  : -like [partial name]......................................................products fitting the partial name\n";
        description += "\t  : -price [max_price]........................................................products under the specified price\n";
        description += "\t  : -price [min_price max_price...............................................products with a price price between price1 and price2\n";

        return description;
    }

    /**
     * @brief Exécute la commande
     * @param cmdline : commande à exécuter
     * @param out : flux d'affichage std
     * @param err : flux d'affichage err
     * @return les données à afficher
     * @throws java.lang.Exception : erreur durant l'exécution de la méthode
     */
    public String execute(String cmdline, PrintStream out, PrintStream err) throws InsuffisantPrivilege {
        StringBuffer res = new StringBuffer();
        EcomCustomerRemote ecomCustomerBean = (EcomCustomerRemote) _context.getVar("customerBean");
        EcomAdminRemote ecomAdminBean = (EcomAdminRemote) _context.getVar("adminBean");
        Outputs currentOutput = (Outputs) _context.getVar("output");
        String currentCurrency = String.valueOf(_context.getVar("currency"));

        Collection<ProductBean> products = null;
        Collection<WarehouseLineBean> stocks = null;

        StringTokenizer st = new StringTokenizer(cmdline, " ");

        // Ignore cmd
        st.nextToken();

        // without arg : list all stores
        if (st.countTokens() == 0) {
            products = ecomAdminBean.getProducts();
            return printProductList(out, products, currentOutput);
        } // With option
        else {
            String option = st.nextToken();

            // adding
            try {
                if (option.equals("-add")) {
                    if (true != _context.getVar("adminMode")) {
                        throw new InsuffisantPrivilege();
                    }
                    if (st.countTokens() == 3) {
                        String name = st.nextToken();
                        String description = st.nextToken();
                        int categIg = Integer.parseInt(st.nextToken());

                        int id = ecomAdminBean.createProduct(name, description, categIg);
                        res.append(_context.returnCode("Product Created with id #" + id, RESULT_ACTION.SUCCESS));
                    } else {
                        res.append(_context.returnCode(Shell.INCORRECT_ARGS_NUMBER, RESULT_ACTION.FAILED));
                    err.println(Shell.INCORRECT_ARGS_NUMBER);
                    }
                } else if (option.equals("-remove")) {
                    if (true != _context.getVar("adminMode")) {
                        throw new InsuffisantPrivilege();
                    }
                    if (st.countTokens() == 1) {
                        int id = Integer.valueOf(st.nextToken());
                        ecomAdminBean.removeProduct(id);
                        res.append(_context.returnCode("Product #" + id + " deleted", RESULT_ACTION.SUCCESS));
                    } else {
                        res.append(_context.returnCode(Shell.INCORRECT_ARGS_NUMBER, RESULT_ACTION.FAILED));
                    err.println(Shell.INCORRECT_ARGS_NUMBER);
                    }
                } else if (option.equals("-addStock")) {
                    if (true != _context.getVar("adminMode")) {
                        throw new InsuffisantPrivilege();
                    }
                    if (st.countTokens() == 8) {
                        if (".p".equalsIgnoreCase(st.nextToken().trim())) {
                            int productId = Integer.valueOf(st.nextToken().trim());
                            if (".s".equalsIgnoreCase(st.nextToken().trim())) {
                                int storeId = Integer.valueOf(st.nextToken().trim());
                                if (".q".equalsIgnoreCase(st.nextToken().trim())) {
                                    int q = Integer.valueOf(st.nextToken().trim());
                                    if (".price".equalsIgnoreCase(st.nextToken().trim())) {
                                        double price = Double.valueOf(st.nextToken().trim());
                                        ecomAdminBean.createStockLine(storeId, productId, q, price);
                                        res.append(_context.returnCode("STOCK ADDED", RESULT_ACTION.SUCCESS));
                                    }
                                }
                            }
                        }
                    } else {
                        err.println("\t'" + option + "' option is not known for Account Management");
                    }
                } else if (option.equals("-id")) {
                    if (st.countTokens() == 1) {
                        int id = Integer.valueOf(st.nextToken());
                        ProductBean product = ecomCustomerBean.getProductById(id);
                        return printProduct(out, product, currentOutput);
                    } else {
                        res.append(_context.returnCode(Shell.INCORRECT_ARGS_NUMBER, RESULT_ACTION.FAILED));
                    err.println(Shell.INCORRECT_ARGS_NUMBER);
                    }
                } else if (option.equalsIgnoreCase("-categ")) {
                    while (st.hasMoreTokens()) {
                        int categId = Integer.valueOf(st.nextToken().trim());
                        stocks = ecomCustomerBean.getProductsByCategory(categId);
                        return printStockList(stocks, ecomCustomerBean, currentCurrency, out, currentOutput);
                    }
                } else if (option.equalsIgnoreCase("-store")) {
                    while (st.hasMoreTokens()) {
                        int storeId = Integer.valueOf(st.nextToken().trim());
                        stocks = ecomCustomerBean.getProductsInStore(storeId);
                        return printStockList(stocks, ecomCustomerBean, currentCurrency, out, currentOutput);
                    }
                } else if (option.equalsIgnoreCase("-name")) {
                    String name = String.valueOf(st.nextToken().trim());
                    stocks = ecomCustomerBean.getProductsByName(name);
                    return printStockList(stocks, ecomCustomerBean, currentCurrency, out, currentOutput);
                } else if (option.equalsIgnoreCase("-like")) {
                    String like = String.valueOf(st.nextToken().trim());
                    stocks = ecomCustomerBean.getProductsBySimilarName(like);
                    return printStockList(stocks, ecomCustomerBean, currentCurrency, out, currentOutput);
                } else if (option.equalsIgnoreCase("-price")) {
                    double firstRange = Double.valueOf(st.nextToken().trim());
                    firstRange = ecomCustomerBean.convertToEuro(firstRange, currentCurrency);

                    if (st.hasMoreTokens()) {
                        double secondRange = Double.valueOf(st.nextToken().trim());
                        secondRange = ecomCustomerBean.convertToEuro(secondRange, currentCurrency);
                        stocks = ecomCustomerBean.getProductsByPriceInterval(firstRange, secondRange);
                    } else {
                        stocks = ecomCustomerBean.getProductsByByMaximumPrice(firstRange);
                    }
                    return printStockList(stocks, ecomCustomerBean, currentCurrency, out, currentOutput);
                }
            } catch (InsuffisantPrivilege e) {
                throw e;
            } catch (UnknownIdentifier ex) {
                res.append(_context.returnCode(ex.toString(), RESULT_ACTION.FAILED));
                err.println(ex);
            } catch (Exception ex) {
                res.append(_context.returnCode(ex.toString(), RESULT_ACTION.FAILED));
                err.println("Error with store warehouse update");
            }
        }
        return res.toString();
    }

    /**
     * @brief Affichage des lignes de stock
     * @param stocks : ligne d'inventaire
     * @param ecomCustomerBean : bean remote client
     * @param currentCurrency : devise active
     * @param out : flux d'affichage std
     * @param currentOutput : sortie attendue
     * @return les données à afficher selon le type de sortie positionné
     */
    private String printStockList(Collection<WarehouseLineBean> stocks, EcomCustomerRemote ecomCustomerBean, String currentCurrency, PrintStream out, Outputs currentOutput) {
        StringBuffer res = new StringBuffer();
        if (currentOutput.equals(Outputs.XML)) {
            Element products = new Element("stocks");
            org.jdom.Document document = new Document(products);

            for (WarehouseLineBean w : stocks) {
                products.addContent(buildXMLStockNode(w, ecomCustomerBean, currentCurrency));
            }

            XMLOutputter sortie = new XMLOutputter(Format.getPrettyFormat());
            res.append(sortie.outputString(document));

        } else if (currentOutput.equals(Outputs.TEXT)) {
            if (!stocks.isEmpty()) {
                res.append("###############" + "\n");
                res.append("# Known stock #" + "\n");
                res.append("###############" + "\n");
                for (WarehouseLineBean w : stocks) {
                    res.append(printStock(out, w, ecomCustomerBean, currentCurrency) + "\n");
                }

            } else {
                res.append("No Stock !");
            }

        } else {
            res.append("\t /!\\ Output format '" + currentOutput + "' is known but not implementing for this command");
        }
        return res.toString();
    }

    /**
     * @brief Affichage de la liste des produits
     * @param out : flux d'affichage std
     * @param symbols : liste des produits disponibles
     * @param currentOutput : type de sortie attendue
     * @return les données à afficher selon le type de sortie attendue
     */
    private String printProductList(PrintStream out, Collection<ProductBean> symbols, Outputs currentOutput) {
        StringBuffer res = new StringBuffer();
        if (currentOutput.equals(Outputs.XML)) {
            Element products = new Element("products");
            org.jdom.Document document = new Document(products);

            for (ProductBean p : symbols) {
                products.addContent(buildXMLProductNode(p));
            }

            XMLOutputter sortie = new XMLOutputter(Format.getPrettyFormat());
            res.append(sortie.outputString(document));

        } else if (currentOutput.equals(Outputs.TEXT)) {
            res.append("##################" + "\n");
            res.append("# Known products #" + "\n");
            res.append("################" + "\n");
            for (ProductBean p : symbols) {
                res.append("\t" + p + "\n");
            }

        } else {
            res.append("\t /!\\ Output format '" + currentOutput + "' is known but not implementing for this command");
        }
        return res.toString();
    }

    /**
     * @brief Affiche un produit
     * @param out : flux d'affichage std
     * @param p : produit à afficher
     * @param currentOutput : sortie attendue
     * @return les données à afficher selon le type de sortie positionné
     */
    private String printProduct(PrintStream out, ProductBean p, Outputs currentOutput) {
        Collection<ProductBean> list = new ArrayList();
        list.add(p);
        return printProductList(out, list, currentOutput);
    }

    /**
     * @brief Affichage d'une ligne de stock (TEXT)
     * @param out : flux d'affichage std
     * @param w : ligne d'inventaire
     * @param ecomCustomerBean : bean remote client
     * @param currentCurrency : devise active
     * @return les données à afficher selon le type de sortie positionné
     */
    private String printStock(PrintStream out, WarehouseLineBean w, EcomCustomerRemote ecomCustomerBean, String currentCurrency) {
        DecimalFormat myFormatter = new DecimalFormat("##0.00");

        return ("Store #" + w.getStore().getReference() + " | " + w.getStore().getName() + " - " +
                "Product #" + w.getProduct().getProductId() + " | " + w.getProduct().getProductName() + "\n\t" +
                "Quantity: " + w.getQuantity() + " - " +
                "Price: " + myFormatter.format(ecomCustomerBean.convertFromEuro(w.getProductPrice(), currentCurrency)) + currentCurrency + "\n\n");
    }

    /**
     * @brief Renvoie un noeud XML décrivant une ligne d'inventaire
     * @param w : ligne d'inventaire
     * @param ecomCustomerBean : bean remote client
     * @param currentCurrency : devise active
     * @return un noeud XML décrivant une ligne d'inventaire
     */
    private Element buildXMLStockNode(WarehouseLineBean w, EcomCustomerRemote ecomCustomerBean, String currentCurrency) {
        Element stock = new Element("stock");

        // product node
        Element product = buildXMLProductNode(w.getProduct());
        stock.addContent(product);

        // store
        Element store = StoreCommandImpl.buildXMLStoreNode(w.getStore(), false, ecomCustomerBean, currentCurrency);
        stock.addContent(store);

        Element quantity = new Element("quantity");
        quantity.setText(String.valueOf(w.getQuantity()));
        stock.addContent(quantity);

        Element price = new Element("price");
        DecimalFormat myFormatter = new DecimalFormat("##0.00");
        price.setText(myFormatter.format(ecomCustomerBean.convertFromEuro(w.getProductPrice(), currentCurrency)));
        Attribute currency = new Attribute("currency", currentCurrency);
        price.setAttribute(currency);
        stock.addContent(price);

        return stock;
    }

    /**
     * @brief renvoie un noeud XML décrivant un produit
     * @param p : produit à afficher
     * @return un noeud XML décrivant un produit
     */
    public static Element buildXMLProductNode(ProductBean p) {
        Element product = new Element("product");
        Attribute id = new Attribute("id", String.valueOf(p.getProductId()));
        product.setAttribute(id);

        Element name = new Element("name");
        name.setText(p.getProductName());
        product.addContent(name);

        Element description = new Element("description");
        description.setText(p.getDescription());
        product.addContent(description);

        Element image = new Element("image");
        image.setText(p.getImage());
        product.addContent(image);

        Element category = new Element("category");
        Attribute categId = new Attribute("id", String.valueOf(p.getCategory().getIdCateg()));
        category.setAttribute(categId);
        category.setText(p.getCategory().getName());
        product.addContent(category);

        return product;
    }
}
